package app

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"gitee.com/leicc/go-app-core/logics"
	"gitee.com/leicc/go-app-core/models/im"
	"gitee.com/leicc/go-core"
	"gitee.com/leicc/go-imsrv-app/app/logic"
	"gitee.com/leicc/go-orm"
	"github.com/gin-gonic/gin"
)

const HTTPJWTKEY = "JWTKEY"

func Register(c *gin.Engine) {
	//上传文件的大小设定 64MB
	baseDir := os.Getenv("BASEDIR")
	if len(baseDir) == 0 {//设置当前基础地址
		baseDir = "./"
	}
	c.MaxMultipartMemory = 64 * 1024 * 1024
	c.LoadHTMLGlob(baseDir+"config/static/*.html")
	c.Static("/static", baseDir+"config/static")
	c.GET("/", index)
	hRouter := c.Group("/api/im")
	hRouter.POST("/nospeak", noSpeak)
	hRouter.POST("/register", doRegister)
	hRouter.POST("/filter/list", filterList)
	hRouter.POST("/filter/create", filterEdit)
	hRouter.POST("/filter/delete", filterDelete)
	hRouter.POST("/session/list", sessionList)
	hRouter.POST("/session/delete", sessionDelete)
	hRouter.POST("/session/create", sessionCreate)
	hRouter.POST("/message/list", messageList)
	hRouter.POST("/message/offline", offlineMessage)
}

//测试界面首页
func index(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", gin.H{
		"ws": "ws://"+c.Request.Host+ logic.GConfig.App.ImSeg,
	})
}

// @Summary 第三方登录对接验证
// @Description 首次交互的话 通过token交互验证获取im-token 注册到redis当中
// @Tags 桃叭IM
// @Param token formData string true "第三方请求的token"
// @Param agent formData string true "登录agent"
// @Param appid formData int true "登录APPID"
// @Success 200 {json} HttpView
// @Router /api/im/register [post]
func doRegister(c *gin.Context) {
	args   := logic.ArgsRegTokenSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	user, err := SysToken(&args)
	if err != nil {//请求校验信息异常的情况
		logics.PanicHttpError(6001, err.Error())
	}
	stime, sorm := time.Now().Unix(), im.NewImUser()
	keyStr := fmt.Sprintf("%x", md5.Sum([]byte(args.Token)))
	lastId := logics.GetId(3) //获取IM注册账号ID
	lastId  = sorm.SetModTable(user.UserId).NewOneFromHandler(func(st *orm.QuerySt) *orm.QuerySt {
		st.Value("id", lastId).Value("role", 0).Value("expire", 0)
		st.Value("appid", args.AppId).Value("userid", user.UserId).Value("regtime", stime)
		st.Value("token", keyStr).Value("lsttime", stime).Value("gender", user.Gender)
		st.Value("nick", user.Nick).Value("avatar", user.Avatar).Value("stime", stime)
		return st
	}, func(st *orm.QuerySt) interface{}{
		st.Duplicate("token", keyStr).Duplicate("lsttime", stime).Duplicate("gender", user.Gender)
		st.Duplicate("nick", user.Nick).Duplicate("avatar", user.Avatar).Duplicate("stime", stime)
		return user.UserId
	})
	if lastId == -1 {//执行出错的情况
		logics.PanicHttpError(6002, "账号注册IM数据库执行异常")
	}
	wsUri := logic.RState.Register(args.AppId, user.UserId, keyStr)
	core.NewHttpView(c).JsonDisplay(gin.H{"vkey": keyStr, "wsUri":wsUri, "user":user})
}

//http请求权限校验 处理逻辑 验证放请求头 userid:vkey
func httpAccessCheck(appid int64, c *gin.Context) int64 {
	jwtstr := c.GetHeader(HTTPJWTKEY)
	if ok, err := regexp.MatchString("[\\d]+:", jwtstr); !ok || err != nil {
		return -1 //请求的参数有问题的情况
	}
	astr := strings.SplitN(jwtstr, ":", 2)
	userId, _ := strconv.ParseInt(astr[0], 10, 64)
	if logic.RState.Vkey(appid, userId) != astr[1] {
		return -1 //请求验证key有问题的情况
	}
	return userId
}

//获取账号数据资料信息
func httpGetUser(appid int64, userid int64) *logic.ImUserSt {
	sorm := im.NewImUser().SetModTable(userid)
	user := logic.ImUserSt{}
	err  := sorm.GetItem(func(st *orm.QuerySt) string {
		st.Where("appid", appid).Where("userid", userid)
		return st.GetWheres()
	}, "*").ToStruct(&user)
	if err != nil && user.Id < 1 {
		logics.PanicHttpError(6001, "获取账号信息失败")
	}
	return &user
}

//获取账号数据资料信息
func httpGetUserInfo(appid int64, userid int64) *logic.UserSt {
	sorm := im.NewImUser().SetModTable(userid)
	user := logic.UserSt{}
	err  := sorm.GetItem(func(st *orm.QuerySt) string {
		st.Where("appid", appid).Where("userid", userid)
		return st.GetWheres()
	}, "userid,nick,avatar,gender").ToStruct(&user)
	if err != nil && user.UserId < 1 {
		logics.PanicHttpError(6001, "获取账号信息失败")
	}
	return &user
}

// @Summary 设置用户禁言解除禁言的操作
// @Description 用户禁言调用这个接口，只有role>1的管理员才能操作
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param appid formData int true "接入的应用ID"
// @Param userid formData array true "IM接入应用用户id"
// @Param expire formData int true "禁言时间 默认300 5分钟"
// @Success 200 {json} HttpView
// @Router /api/im/nospeak [post]
func noSpeak(c *gin.Context) {
	args := logic.ArgsNoSpeak{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	//必须是管理员角色 才能设置禁言
	userId := httpAccessCheck(args.AppId, c)
	sorm   := im.NewImUser().SetModTable(userId)
	user   := httpGetUser(args.AppId, userId)
	if user == nil || user.Role < 1 || user.Id < 1 {//非管理员，无法操作
		logics.PanicHttpError(6002, "非管理员,无法操作")
	}
	nsize  := 0
	for _, idStr := range args.UserIds {//处理禁言的逻辑
		userId, _ = strconv.ParseInt(idStr, 10, 64)
		expire := logic.RState.SetNoSpeak(args.AppId, userId, args.ExpireAt)
		sorm.SetModTable(userId).MultiUpdate(func(st *orm.QuerySt) string {
			st.Where("appid", args.AppId).Where("userid", userId)
			return st.GetWheres()
		}, func(st *orm.QuerySt) *orm.QuerySt {
			st.Value("expire", expire)
			return st
		})
		nsize++
	}
	core.NewHttpView(c).JsonDisplay(gin.H{"nsize":nsize}) //格式化输出数据
}

// @Summary 删除黑名单用户
// @Description 删除黑名单之后，用户将在聊天会话中看到用户
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param appid formData int true "对应的应用ID"
// @Param ids formData array true "要删除的记录数组"
// @Success 200 {json} HttpView
// @Router /api/im/filter/delete [post]
func filterDelete(c *gin.Context)  {
	args := logic.ArgsIdsSliceSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	userId := httpAccessCheck(args.AppId, c)
	intSlice := logics.StrSliceToIntSlice(args.Ids)
	if len(intSlice) < 1 {//过滤请求删除的ID记录
		logics.PanicHttpError(6001, "请设置要删除的ID记录")
	}
	sorm   := im.NewImFilter().SetModTable(userId)
	rows   := sorm.MultiDelete(func(st *orm.QuerySt) string {
		st.Where("id", intSlice, orm.OP_IN).Where("userid", userId)
		return st.GetWheres()
	})
	core.NewHttpView(c).JsonDisplay(gin.H{"rows": rows})
}

// @Summary 编辑黑名单
// @Description 拉入黑名单之后，用户将再聊天会话中屏蔽用户
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param appid formData int true "对应的应用ID"
// @Param touserid formData string true "拉入黑名单的用户ID"
// @Param remark formData string true "用户备注信息"
// @Param touser formData object true "加入黑名单的用户对象"
// @Success 200 {json} HttpView
// @Router /api/im/filter/create [post]
func filterEdit(c *gin.Context)  {
	args := logic.ArgsFilterSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	userId, stime := httpAccessCheck(args.AppId, c), time.Now().Unix()
	aorm   := im.NewImUser().SetModTable(userId)
	oldid  := aorm.GetValue(func(st *orm.QuerySt) string {
		st.Where("appid", args.AppId).Where("userid", userId)
		return st.GetWheres()
	}, "userid").ToInt64()
	if oldid < 1 || oldid != userId {
		logics.PanicHttpError(6001, "账号Appid不一致,无法操作")
	}
	sorm   := im.NewImFilter().SetModTable(userId)
	touser, _ := json.Marshal(args.ToUser) //格式化json
	lastId := sorm.NewOneFromHandler(func(st *orm.QuerySt) *orm.QuerySt {
		st.Value("touserid", args.ToUserId).Value("remark", args.Remark).Value("appid", args.AppId)
		st.Value("stime", stime).Value("userid", userId).Value("touser", string(touser))
		return st
	}, func(st *orm.QuerySt) interface{} {
		st.Duplicate("remark", args.Remark).Duplicate("touser", string(touser))
		return nil
	})
	core.NewHttpView(c).JsonDisplay(gin.H{"id": lastId})
}

// @Summary 获取黑名单用户数据资料信息
// @Description 拉取我的设置的黑名单用户列表
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param query formData string true "检索数据资料信息"
// @Param appid formData int true "对应的应用ID编号"
// @Param offset formData int true "拉取数据偏移量"
// @Param limit formData int true "获取记录数量"
// @Success 200 {json} HttpView
// @Router /api/im/filter/list [post]
func filterList(c *gin.Context)  {
	args := logic.ArgsListSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	userId := httpAccessCheck(args.AppId, c)
	sorm   := im.NewImFilter().SetModTable(userId)
	list   := sorm.GetList(args.Offset, args.Limit, func(st *orm.QuerySt) string {
		st.Where("userid", userId).Where("appid", args.AppId)
		args.Query = strings.TrimSpace(args.Query)
		if len(args.Query) > 0 {
			st.Where("(", "")
			st.Where("remark", "%"+args.Query+"%", orm.OP_LIKE)
			st.Where("touser", "%"+args.Query+"%", orm.OP_LIKE, orm.OP_OR)
			st.Where(")", "")
		}
		return st.GetWheres()
	}, "id,touserid,touser,remark,stime", "id", orm.DESC)
	core.NewHttpView(c).JsonDisplay(gin.H{"list": list})
}

// @Summary 获取IM会话数据资料信息
// @Description 拉取聊天会话信息列表，这个列表进入具体的聊天界面
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param query formData string true "检索数据资料信息"
// @Param appid formData int true "对应的应用ID编号"
// @Param offset formData int true "拉取数据偏移量"
// @Param limit formData int true "获取记录数量"
// @Success 200 {json} HttpView
// @Router /api/im/session/list [post]
func sessionList(c *gin.Context) {
	args := logic.ArgsListSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	userId:= httpAccessCheck(args.AppId, c)
	torm  :=  im.NewImFilter().SetModTable(userId)
	filter:= torm.GetColumn(0, -1, func(st *orm.QuerySt) string {
		st.Where("userid", userId).Where("appid", args.AppId)
		return st.GetWheres()
	}, "touserid")
	sorm := im.NewImSession().SetModTable(userId)
	list := sorm.GetList(args.Offset, args.Limit, func(st *orm.QuerySt) string {
		st.Where("userid", userId, orm.OP_EQ)
		if filter != nil && len(filter) > 0 {//黑名单数量存在的情况
			st.Where("touserid", filter, orm.OP_NOTIN)
		}
		args.Query = strings.TrimSpace(args.Query)
		if len(args.Query) > 0 {//模糊检索的处理逻辑
			st.Where("(", "")
			st.Where("touser", "%"+args.Query+"%", orm.OP_LIKE)
			st.Where("lstmsg", "%"+args.Query+"%", orm.OP_LIKE, orm.OP_OR)
			st.Where(")", "")
		}
		return st.GetWheres()
	}, "id,touserid,touser,targetid,type,object,lstmsg,stime", "stime", orm.DESC)
	core.NewHttpView(c).JsonDisplay(gin.H{"list": list})
}

// @Summary 删除指定的会话
// @Description 删除指定的会话，不想看到这个会话了
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param appid formData int true "对应的应用ID编号"
// @Param ids formData int true "会话记录ID"
// @Success 200 {json} HttpView
// @Router /api/im/session/delete [post]
func sessionDelete(c *gin.Context) {
	args := logic.ArgsIdsSliceSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	userId   := httpAccessCheck(args.AppId, c)
	intSlice := logics.StrSliceToIntSlice(args.Ids)
	if len(intSlice) < 1 {//过滤请求删除的ID记录
		logics.PanicHttpError(6001, "请设置要删除的ID记录")
	}
	sorm := im.NewImSession().SetModTable(userId)
	rows := sorm.MultiDelete(func(st *orm.QuerySt) string {
		st.Where("id", intSlice, orm.OP_IN).Where("appid", args.AppId)
		st.Where("userid", userId, orm.OP_EQ)
		return st.GetWheres()
	})
	core.NewHttpView(c).JsonDisplay(gin.H{"rows": rows})
}


// @Summary 设置用户聊天会话
// @Description 用户发送提一条消息的时候就要发送一个请求创建一个聊天会话，聊天退出的时候提交一下
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param appid formData int true "应用的ID"
// @Param touserid formData string true "会话对象ID"
// @Param touser formData object true "会话对象信息"
// @Param targetid formData string true "会话的主线ID"
// @Param type formData int true "会话类别，0-私聊 1-频道群聊 2-世界频道 等等"
// @Param object formData string true "聊天顶部的话对象"
// @Param lstmsg formData string true "最近一条聊天信息"
// @Success 200 {json} HttpView
// @Router /api/im/session/create [post]
func sessionCreate(c *gin.Context) {
	args := logic.ArgsConversationSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	lastId, userId := int64(0), httpAccessCheck(args.AppId, c)
	sndUserInfo := httpGetUserInfo(args.AppId, userId)
	sndUser, _  := json.Marshal(sndUserInfo)
	toUser, _   := json.Marshal(args.ToUser)
	lstMsg, _   := json.Marshal(args.LastMsg)
	if args.TargetId < 1 { //分表策略处理逻辑
		args.TargetId = logic.GStore.GenTargetid(userId, args.ToUserId)
	}
	stime, sorm := time.Now().Unix(), im.NewImSession()
	dfields := orm.SqlMap{"appid":args.AppId, "userid":userId, "user":string(sndUser), "touserid":args.ToUserId, "touser":string(toUser),
		"targetid":args.TargetId, "type":args.Type, "object":args.Object, "neartime":stime, "lstmsg":string(lstMsg), "stime":stime}
	xfields := orm.SqlMap{"object":args.Object, "neartime":stime, "lstmsg":string(lstMsg), "user":string(sndUser), "touser":string(toUser), "stime":stime}
	sorm.Query().Transaction(func(st *orm.QuerySt) bool {
		lastId = sorm.SetModTable(userId).NewOne(dfields, xfields) //更新数值资料信息
		if lastId < 1 {//失败的情况
			return false
		}
		if args.Type == 0 && args.TargetId > 0 {//如果是私人聊天的情况多条记录
			dfields.Merge(orm.SqlMap{"userid":args.ToUserId, "user":string(toUser), "touserid":userId, "touser":string(sndUser)})
			xfields.Merge(orm.SqlMap{"user":string(toUser), "touser":string(sndUser)})
			if sorm.SetModTable(args.ToUserId).NewOne(dfields, xfields) < 1 {//失败的情况
				return false
			}
		}
		return true
	})
	core.NewHttpView(c).JsonDisplay(gin.H{"id": lastId})
}


// @Summary 获取指定会话聊天数据信息
// @Description 首次进入会话的时候拉取历史聊天数据信息
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param appid formData int true "应用ID信息"
// @Param minid formData int true "取这个id之前的数据"
// @Param touserid formData int true "聊天对象ID"
// @Param type formData int true "聊天类型 0-私聊 1-频道群聊 2-世界频道"
// @Param targetid formData int true "聊天目标id"
// @Success 200 {json} HttpView
// @Router /api/im/message/list [post]
func messageList(c *gin.Context) {
	args := logic.ArgsMessageListSt{}
	if err := c.ShouldBind(&args); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	userId := httpAccessCheck(args.AppId, c)
	if args.TargetId < 1 {//数据小于1的情况
		args.TargetId = logic.GStore.GenTargetid(userId, args.ToUserId)
	}
	sorm   := im.NewImMessage().SetModTable(args.TargetId)
	list   := sorm.GetList(0, 50, func(st *orm.QuerySt) string {
		st.Where("targetid", args.TargetId).Where("type", args.Type).Where("appid", args.AppId)
		if args.Type == 0 {//私人聊天的情况
			st.Where("(", "").Where("(", "").Where("userid", userId)
			st.Where("touserid", args.ToUserId).Where(")", "")
			st.Where("(", "", orm.OP_OR).Where("userid", args.ToUserId)
			st.Where("touserid", userId).Where(")", "").Where(")", "")
		}
		if args.MinId > 0 {//取ID比较小的记录信息
			st.Where("id", args.MinId, orm.OP_LT)
		}
		return st.GetWheres()
	}, "id,userid,user,touserid,touser,message,stime", "id", orm.DESC)
	core.NewHttpView(c).JsonDisplay(gin.H{"list": list})
}

// @Summary 设置存储离线消息
// @Description 设置离线消息，当im连接失败或者发送失败的情况批量提交到该接口
// @Tags 桃叭IM
// @Param SIGNATURE header string true "对接第三方登录的时候获取的token"
// @Param touserid formData int true "聊天对象ID"
// @Param touser formData object true "聊天对象信息"
// @Param type formData int true "聊天类型"
// @Param targetid formData int true "聊天目标id"
// @Param messages formData object true "具体的聊天内容"
// @Success 200 {json} HttpView
// @Router /api/im/message/offline [post]
func offlineMessage(c *gin.Context) {
	msgSt := logic.ArgsOfflineMessages{Messages: []logic.MessageSt{}}
	if err := c.ShouldBind(&msgSt); err != nil {
		logics.PanicValidateHttpError(1001, err)
	}
	userId := httpAccessCheck(msgSt.AppId, c)
	if logic.RState.IsNoSpeak(msgSt.AppId, userId) {
		logics.PanicHttpError(6001, "您已被禁言,无法发言")
	}
	if msgSt.Type == 0 && logic.RState.IsFilter(msgSt.AppId, userId, msgSt.ToUserId) {
		logics.PanicHttpError(6002, "对方设置了黑名单,无法给TA留言")
	}
	sndUser := httpGetUserInfo(msgSt.AppId, userId)
	msgSt.UserId, msgSt.User = userId, *sndUser
	logic.GStore.Send(msgSt) //数据不为空的情况
	core.NewHttpView(c).JsonDisplay(nil)
}